<html>
	<head>
		<title>aspects</title>
		<style type="text/css">
			@import "../../../dojo/resources/dojo.css";
		</style>
		<script type="text/javascript" src="../../../dojo/dojo.js" djConfig="isDebug:true"></script>
		<script type="text/javascript" src="../aspect.js"></script>
		<script type="text/javascript" src="../aspect/cflow.js"></script>
		<script type="text/javascript" src="../aspect/tracer.js"></script>
		<script type="text/javascript" src="../aspect/timer.js"></script>
		<script type="text/javascript" src="../aspect/profiler.js"></script>
		<script type="text/javascript" src="../aspect/counter.js"></script>
		<script type="text/javascript" src="../aspect/memoizer.js"></script>
		<script type="text/javascript" src="../aspect/memoizerGuard.js"></script>
		<script type="text/javascript">
			dojo.require("dojox.lang.aspect");
			dojo.require("dojox.lang.aspect.cflow");
			dojo.require("dojox.lang.aspect.tracer");
			dojo.require("dojox.lang.aspect.timer");
			dojo.require("dojox.lang.aspect.profiler");
			dojo.require("dojox.lang.aspect.counter");
			dojo.require("dojox.lang.aspect.memoizer");
			dojo.require("dojox.lang.aspect.memoizerGuard");
			dojo.require("dojo.string");
			dojo.require("dojox.lang.functional.listcomp");
			
			var test = function(){
				// This is a test class, don't program like this!
				var Rect = function(){
					this.x = this.y = this.width = this.height = 0;
				};
				dojo.extend(Rect, {
					// getters
					getX: function(){ return this.x; },
					getY: function(){ return this.y; },
					getWidth: function(){ return this.width; },
					getHeight: function(){ return this.height; },
					// setters
					setX: function(val){ this.x = val; },
					setY: function(val){ this.y = val; },
					setWidth: function(val){ this.width = val; },
					setHeight: function(val){ this.height = val; },
					// special methods
					getPerimeter: function(){ return 2 * (this.width + this.height); },
					getArea: function(){ return this.width * this.height; },
					getCorner: function(){ return {x: this.x + this.width, y: this.y + this.height}; },
					move: function(x, y){ this.x = x; this.y = y; },
					makeSquare: function(l){ this.width = this.height = l; },
					scale: function(s){ this.width *= s; this.height *= s; },
					pointInside: function(x, y){
						return	this.x <= x && x < (this.x + this.width) &&
								this.y <= y && y < (this.y + this.height);
					},
					assertSquare: function(){
						if(this.getHeight() != this.getWidth()){
							throw new Error("NOT A SQUARE!");
						}
					}
				});
				
				var aop = dojox.lang.aspect, df = dojox.lang.functional;
				
				// our simple advices
				var TraceArguments = {
					before: function(/*arguments*/){
						var joinPoint = aop.getContext().joinPoint,
							args = Array.prototype.join.call(arguments, ", ");
						console.log("=> " + joinPoint.targetName + "(" + args + ")");
					}
				};
				var TraceReturns = {
					afterReturning: function(retVal){
						var joinPoint = aop.getContext().joinPoint;
						console.log("<= " + joinPoint.targetName + " returns " + retVal);
					},
					afterThrowing: function(excp){
						var joinPoint = aop.getContext().joinPoint;
						console.log("<= " + joinPoint.targetName + " throws: " + excp);
					}
				};
				
				console.log("create rect1 and call its methods without aspects.");
				var rect1 = new Rect;
				rect1.move(100, 100);
				rect1.makeSquare(200);
				rect1.pointInside(150, 250);
				console.log("perimeter: " + rect1.getPerimeter());
				console.log("area: " + rect1.getArea());
				console.log("=================================");
				
				console.log("create rect2, attach advices to the instance, and repeat...");
				var rect2 = new Rect;
				aop.advise(rect2, /^get/, TraceReturns);
				aop.advise(rect2, [/^set/, "move", "makeSquare"], TraceArguments);
				aop.advise(rect2, "pointInside", [TraceReturns, TraceArguments]);
				rect2.move(100, 100);
				rect2.makeSquare(200);
				rect2.pointInside(150, 250);
				console.log("perimeter: " + rect2.getPerimeter());
				console.log("area: " + rect2.getArea());
				console.log("=================================");

				console.log("attach advices to the Rect class, and repeat with rect1...");
				var h1 = aop.advise(Rect, /^get/, TraceReturns);
				var h2 = aop.advise(Rect, [/^set/, "move", "makeSquare"], TraceArguments);
				var h3 = aop.advise(Rect, "pointInside", [TraceReturns, TraceArguments]);
				rect1.move(100, 100);
				rect1.makeSquare(200);
				rect1.pointInside(150, 250);
				console.log("perimeter: " + rect1.getPerimeter());
				console.log("area: " + rect1.getArea());
				console.log("=================================");
				
				console.log("remove advices for getters and setters from the class, and repeat with rect1...");
				aop.unadvise(h1);
				aop.unadvise(h2);
				rect1.move(100, 100);
				rect1.makeSquare(200);
				rect1.pointInside(150, 250);
				console.log("perimeter: " + rect1.getPerimeter());
				console.log("area: " + rect1.getArea());
				console.log("=================================");

				console.log("repeat with rect2...");
				rect2.move(100, 100);
				rect2.makeSquare(200);
				rect2.pointInside(150, 250);
				console.log("perimeter: " + rect2.getPerimeter());
				console.log("area: " + rect2.getArea());
				console.log("=================================");

				console.log("test rect2 with throwing an exception...");
				aop.advise(rect2, /^assert/, TraceReturns);
				try{
					rect2.assertSquare();
					rect2.width = 300; // triggering exception
					rect2.assertSquare();
				}catch(e){
					// squelch
				}
				console.log("=================================");
				
				// more complex dynamic tracing advice
				var Trace = function(context){
					this.name = context.joinPoint.targetName;
				};
				dojo.extend(Trace, {
					before: function(/*arguments*/){
						this.args = Array.prototype.join.call(arguments, ", ");
					},
					afterReturning: function(retVal){
						var buf = "-- " + this.name + "(" + this.args + ")";
						if(typeof retVal == "undefined"){
							// procedure without a return value
							console.log(buf);
						}else{
							// function with returned value
							console.log(buf + " returns: " + retVal);
						}
					},
					afterThrowing: function(excp){
						console.log("-- " + this.name + "(" + this.args + ") throws: " + excp);
					}
				});
				
				// remove tracing for pointInside
				aop.unadvise(h3);
				
				console.log("create rect3, and trace all its methods...");
				var rect3 = new Rect;
				aop.advise(rect3, /^\S/, Trace);
				rect3.move(100, 100);
				rect3.makeSquare(200);
				rect3.pointInside(150, 250);
				console.log("perimeter: " + rect3.getPerimeter());
				console.log("area: " + rect3.getArea());
				rect3.assertSquare();
				console.log("=================================");
				
				var TraceAll = function(context, id){
					this.name = context.joinPoint.targetName;
					this.prefix = dojo.string.pad("", context.depth * 2, "--", true) + "-- #" + (id || 1);
				};
				dojo.extend(TraceAll, {
					before: function(/*arguments*/){
						var args = Array.prototype.join.call(arguments, ", ");
						console.log(this.prefix + " => before " + this.name + "(" + args + ")");
					},
					around: function(/*arguments*/){
						var args = Array.prototype.join.call(arguments, ", ");
						console.log(this.prefix + " => around " + this.name + "(" + args + ")");
						var retVal = aop.proceed.apply(null, arguments);
						console.log(this.prefix + " <= around " + this.name + " returns " + retVal);
						return retVal;	// should return a value, if the target returns a value
					},
					afterReturning: function(retVal){
						console.log(this.prefix + " <= afterR " + this.name + " returns " + retVal);
					},
					afterThrowing: function(excp){
						console.log(this.prefix + " <= afterT " + this.name + " throws: " + excp);
					},
					after: function(){
						console.log(this.prefix + " <= after  " + this.name);
					}
				});
				
				console.log("create rect4, and attach two tracer to all its methods...");
				var rect4 = new Rect;
				aop.advise(rect4, /^\S/, [
					function(context){ return new TraceAll(context, 1); },
					function(context){ return new TraceAll(context, 2); }
				]);
				rect4.move(100, 100);
				rect4.makeSquare(200);
				rect4.pointInside(150, 250);
				console.log("perimeter: " + rect4.getPerimeter());
				console.log("area: " + rect4.getArea());
				try{
					rect4.assertSquare();
					rect4.width = 300; // triggering exception
					rect4.assertSquare();
				}catch(e){
					// squelch
				}
				console.log("=================================");

				var TraceTopLevel = function(context){
					this.name = context.joinPoint.targetName;
				};
				dojo.extend(TraceTopLevel, {
					before: function(/*arguments*/){
						var args = Array.prototype.join.call(arguments, ", ");
						console.log("=> " + this.name + "(" + args + ")");
					},
					afterReturning: function(retVal){
						console.log("<= " + this.name + " returns: " + retVal);
					},
					afterThrowing: function(excp){
						console.log("<= " + this.name + " throws: " + excp);
					}
				});

				console.log("create rect5, and track only top-level calls...");
				var rect5 = new Rect;
				aop.advise(rect5, /^\S/,
					function(context){
						return aop.cflow(context.instance) ? 	// the advised object
							{} :								// do nothing
							new TraceTopLevel(context);			// log top level
					}
				);
				rect5.move(100, 100);
				rect5.makeSquare(200);
				rect5.pointInside(150, 250);
				console.log("perimeter: " + rect5.getPerimeter());
				console.log("area: " + rect5.getArea());
				try{
					rect5.assertSquare();
					rect5.width = 300; // triggering exception
					rect5.assertSquare();
				}catch(e){
					// squelch
				}
				console.log("=================================");
				
				var log = function(){
					// log the rect1pointInside state
					var dispatcher = "native";
					if(rect1.pointInside.target){
						if(rect1.pointInside.advices){
							dispatcher = "dojox.lang.aspect";
						}else if(rect1.pointInside._listeners){
							dispatcher = "dojo.connect";
						}
					}
					console.log("Dispatcher: " + dispatcher);
				};
				
				console.log("use dojo.connect() on rect1 to trace...");
				console.log("Running native method.");
				log();
				rect1.pointInside(150, 250);
				console.log("Connecting an event processor.");
				h1 = dojo.connect(rect1, "pointInside", function(){
					var args = Array.prototype.join.call(arguments, ", ");
					console.log("from dojo.connect(): " + args);
				});
				log();
				rect1.pointInside(150, 250);
				console.log("Connecting the TraceAll advice.");
				h2 = aop.advise(rect1, "pointInside", TraceAll);
				log();
				rect1.pointInside(150, 250);
				console.log("Disconnecting the event processor.");
				dojo.disconnect(h1);
				log();
				rect1.pointInside(150, 250);
				console.log("Disconnecting the advise.");
				aop.unadvise(h2);
				log();
				rect1.pointInside(150, 250);
				console.log("Connecting the TraceAll advice.");
				h2 = aop.advise(rect1, "pointInside", TraceAll);
				log();
				rect1.pointInside(150, 250);
				console.log("Connecting an event processor.");
				h1 = dojo.connect(rect1, "pointInside", function(){
					var args = Array.prototype.join.call(arguments, ", ");
					console.log("from dojo.connect(): " + args);
				});
				log();
				rect1.pointInside(150, 250);
				console.log("Disconnecting the advise.");
				aop.unadvise(h2);
				log();
				rect1.pointInside(150, 250);
				console.log("Disconnecting the event processor.");
				dojo.disconnect(h1);
				log();
				rect1.pointInside(150, 250);
				console.log("=================================");
				
				console.log("trace all methods of rect1...");
				h1 = aop.advise(rect1, /^\S/, aop.tracer(true));
				rect1.move(100, 100);
				rect1.makeSquare(200);
				rect1.pointInside(150, 250);
				console.log("perimeter: " + rect1.getPerimeter());
				console.log("area: " + rect1.getArea());
				try{
					rect1.assertSquare();
					rect1.width = 300; // triggering exception
					rect1.assertSquare();
				}catch(e){
					// squelch
				}
				aop.unadvise(h1);
				console.log("=================================");
				
				console.log("count all get* methods of rect1...");
				var counter = aop.counter();
				h1 = aop.advise(rect1, /^get/, counter);
				rect1.move(100, 100);
				rect1.makeSquare(200);
				rect1.pointInside(150, 250);
				console.log("perimeter: " + rect1.getPerimeter());
				console.log("area: " + rect1.getArea());
				try{
					rect1.assertSquare();
					rect1.width = 300; // triggering exception
					rect1.assertSquare();
				}catch(e){
					// squelch
				}
				aop.unadvise(h1);
				console.log("get* methods were called", counter.calls, "times, with", counter.errors, "errors.");
				console.log("=================================");

				console.log("time all methods of rect1...");
				h1 = aop.advise(rect1, /^\S/, aop.timer());
				rect1.move(100, 100);
				rect1.makeSquare(200);
				rect1.pointInside(150, 250);
				console.log("perimeter: " + rect1.getPerimeter());
				console.log("area: " + rect1.getArea());
				try{
					rect1.assertSquare();
					rect1.width = 300; // triggering exception
					rect1.assertSquare();
				}catch(e){
					// squelch
				}
				aop.unadvise(h1);
				console.log("=================================");

				/*
				console.log("profile all methods of rect1...");
				h1 = aop.advise(rect1, /^\S/, aop.profiler("Profile1"));
				rect1.move(100, 100);
				rect1.makeSquare(200);
				rect1.pointInside(150, 250);
				console.log("perimeter: " + rect1.getPerimeter());
				console.log("area: " + rect1.getArea());
				try{
					rect1.assertSquare();
					rect1.width = 300; // triggering exception
					rect1.assertSquare();
				}catch(e){
					// squelch
				}
				aop.unadvise(h1);
				console.log("=================================");
				*/
				
				var Fibonacci = function(order){
					if(arguments.length == 1){
						this.setOrder(order);
					}else{
						this.offset = 2;
					}
				}
				dojo.extend(Fibonacci, {
					setOrder: function(order){
						this.offset = order + 1;
					},
					getOrder: function(){
						return this.offset - 1;
					},
					calculate: function(n){
						if(n < 0){ return 0; }
						if(n == 0){ return 1; }
						return this.calculate(n - 1) + this.calculate(n - this.offset);
					},
					calculateN: function(n, o){
						if(n < 0){ return 0; }
						if(n == 0){ return 1; }
						return this.calculateN(n - 1, o) + this.calculateN(n - 1 - o, o);
					}
				});
				var args = df.listcomp("i for(i = 0; i < 15; ++i)");

				console.log("calculate Fibonacci numbers...");
				var fib = new Fibonacci;
				h1 = aop.advise(fib, /^calculate/, aop.timer("fib"));
				
				console.log("before memoization");
				fib.setOrder(0);
				console.log("0-order:", dojo.map(args, dojo.hitch(fib, "calculate")));
				fib.setOrder(1);
				console.log("1-order:", dojo.map(args, dojo.hitch(fib, "calculate")));
				fib.setOrder(2);
				console.log("2-order:", dojo.map(args, dojo.hitch(fib, "calculate")));
				fib.setOrder(3);
				console.log("3-order:", dojo.map(args, dojo.hitch(fib, "calculate")));
				
				console.log("after memoization");
				h2 = aop.advise(fib, "calculate", aop.memoizer());
				h3 = aop.advise(fib, /^set/, aop.memoizerGuard("calculate"));
				fib.setOrder(0);
				console.log("0-order:", dojo.map(args, dojo.hitch(fib, "calculate")));
				fib.setOrder(1);
				console.log("1-order:", dojo.map(args, dojo.hitch(fib, "calculate")));
				fib.setOrder(2);
				console.log("2-order:", dojo.map(args, dojo.hitch(fib, "calculate")));
				fib.setOrder(3);
				console.log("3-order:", dojo.map(args, dojo.hitch(fib, "calculate")));
				aop.unadvise(h3);
				aop.unadvise(h2);
				
				console.log("before memoization");
				console.log(fib.calculateN(15, 0));
				console.log(fib.calculateN(15, 1));
				console.log(fib.calculateN(15, 2));
				console.log(fib.calculateN(15, 3));
				
				console.log("after memoization");
				h2 = aop.advise(fib, "calculateN", aop.memoizer(function(a, b){ return a + "/" + b; }));
				h3 = aop.advise(fib, /^set/, aop.memoizerGuard("calculate"));
				console.log(fib.calculateN(15, 0));
				console.log(fib.calculateN(15, 1));
				console.log(fib.calculateN(15, 2));
				console.log(fib.calculateN(15, 3));
				aop.unadvise(h3);
				aop.unadvise(h2);

				aop.unadvise(h1);
				
				console.log("=================================");
			};
			//dojo.addOnLoad(test);
		</script>
	</head>
	<body>
		<p>This test is meant to run with Firebug. Open the console to see the output.</p>
		<p><button onclick="test()">Start</button></p>
	</body>
</html>
